﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace ImageProcessing
{
	class ColorConversions
	{
		private static double[,] adRGBtoYIQ = new double[,] {
			{0.298895,0.586622,0.114482},
			{.595716, -.274453, -.321263},
			{.211456, -.522591, .311135}
		};

		private static double[,] adYIQtoRGB = new double[,] { 
			{1, .9563, .6210},
			{1, -.2721, -.6474},
			{1, -1.1070,1.7046}
		};

		private static double[,] adPitasRGBtoHSI = new double[,] {
			{0.5773503, 0.5773503,0.5773503},
			{0, 0.7071068, -0.7071068},
			{0.8164966, -0.4082483,-0.4082483}
		};

		private static double[,] adPitasHSItoRGB = new double[,] {
			{0.5773503, 0, 0.8164966},
			{ 0.5773503, 0.7071068, -0.4082483},
			{0.5773503, -0.7071068, -0.4082483}
		};

		private static double[,] adPrattRGBtoHSI = new double[,] {
			{0.3333333,0.3333333,0.3333333},
			{-0.4082483,-0.4082483,0.8164966},
			{0.4082483,-0.4082483,0}
		};

		private static double[,] adPrattHSItoRGB = new double[,] {
			{1, -t, 3*t },
			{1, -t, -3*t},
			{1, 2*t, 0 }
		};

		private static double t = 1 / Math.Sqrt(6);


		//converts between RGB and YIQ uses clamping to solve boundary problems.
		public static double[] YIQConversion(double[] colorVector, bool isRGBtoYIQ)
		{

			double[] adConverted = new double[3];

			if (isRGBtoYIQ)
			{
				//normalize RGB to [0,1]
				colorVector[0] = colorVector[0] / 255.0;
				colorVector[1] = colorVector[1] / 255.0;
				colorVector[2] = colorVector[2] / 255.0;
				//calculate coversion
				adConverted[0] = Math.Max(-1.0,Math.Min(1.0,colorVector[0] * adRGBtoYIQ[0,0] + colorVector[1] * adRGBtoYIQ[0,1] + colorVector[2] * adRGBtoYIQ[0,2]));
				adConverted[1] = Math.Max(-.5957,Math.Min(.5957,colorVector[0] * adRGBtoYIQ[1,0] + colorVector[1] * adRGBtoYIQ[1,1] + colorVector[2] * adRGBtoYIQ[1,2]));
				adConverted[2] = Math.Max(-.5226,Math.Min(.5226,colorVector[0] * adRGBtoYIQ[2,0] + colorVector[1] * adRGBtoYIQ[2,1] + colorVector[2] * adRGBtoYIQ[2,2]));
			}
			else
			{
				adConverted[0] = Math.Max(0.0,Math.Min(1.0,colorVector[0] * adYIQtoRGB[0,0] + colorVector[1] * adYIQtoRGB[0,1] + colorVector[2] * adYIQtoRGB[0,2]));
				adConverted[1] = Math.Max(0.0,Math.Min(1.0,colorVector[0] * adYIQtoRGB[1,0] + colorVector[1] * adYIQtoRGB[1,1] + colorVector[2] * adYIQtoRGB[1,2]));
				adConverted[2] = Math.Max(0.0,Math.Min(1.0,colorVector[0] * adYIQtoRGB[2,0] + colorVector[1] * adYIQtoRGB[2,1] + colorVector[2] * adYIQtoRGB[2,2]));
				
			}
			return adConverted;

		}

		public static double GetYChannel (int[] rgb, bool isRGBtoYIQ)
		{
			return rgb[0] * adRGBtoYIQ[0, 0] + rgb[1] * adRGBtoYIQ[0, 1] + rgb[2] * adRGBtoYIQ[0, 2];
		}

		public static double GetYChannel(byte r, byte g, byte b, bool isRGBtoYIQ)
		{
			return r * adRGBtoYIQ[0, 0] + g * adRGBtoYIQ[0, 1] + b * adRGBtoYIQ[0, 2];
		}

		//converts between HSI and RGB using pratt and pitas matrices
		public static double[] HSIConversion(double[] colorVector, bool isRGBtoHSI, bool isPitas)
		{
			double[] adConverted = new double[3];

			if (isRGBtoHSI)
			{
				if (colorVector[0] > 0.0 && colorVector[0] <= 1.0) //normalize RGB to [0,1]
				{
					colorVector[0] = colorVector[0] / 255.0;
					colorVector[1] = colorVector[1] / 255.0;
					colorVector[2] = colorVector[2] / 255.0;
				}
				if (isPitas)
				{
					adConverted[0] = colorVector[0] * adPitasRGBtoHSI[0, 0] + colorVector[1] * adPitasRGBtoHSI[0,1] + colorVector[2] * adPitasRGBtoHSI[0,2];
					adConverted[1] = colorVector[0] * adPitasRGBtoHSI[1, 0] + colorVector[1] * adPitasRGBtoHSI[1,1] + colorVector[2] * adPitasRGBtoHSI[1,2];
					adConverted[2] = colorVector[0] * adPitasRGBtoHSI[2,0] + colorVector[1] * adPitasRGBtoHSI[2,1] + colorVector[2] * adPitasRGBtoHSI[2,2];
				}
				else //Pratt
				{
					adConverted[0] = colorVector[0] * adPrattRGBtoHSI[0, 0] + colorVector[1] * adPrattRGBtoHSI[0,1] + colorVector[2] * adPrattRGBtoHSI[0,2];
					adConverted[1] = colorVector[0] * adPrattRGBtoHSI[1,0] + colorVector[1] * adPrattRGBtoHSI[1,1] + colorVector[2] * adPrattRGBtoHSI[1,2];
					adConverted[2] = colorVector[0] * adPrattRGBtoHSI[2,0] + colorVector[1] * adPrattRGBtoHSI[2,1] + colorVector[2] * adPrattRGBtoHSI[2,2];
				}
			}
			else //HSI to RGB
			{
				if (isPitas)
				{
					adConverted[0] = colorVector[0] * adPitasHSItoRGB[0, 0] + colorVector[1] * adPitasHSItoRGB[0,1] + colorVector[2] * adPitasHSItoRGB[0,2];
					adConverted[1] = colorVector[0] * adPitasHSItoRGB[1,0] + colorVector[1] * adPitasHSItoRGB[1,1] + colorVector[2] * adPitasHSItoRGB[1,2];
					adConverted[2] = colorVector[0] * adPitasHSItoRGB[2,0] + colorVector[1] * adPitasHSItoRGB[2,1] + colorVector[2] * adPitasHSItoRGB[2,2];
				}
				else
				{
					adConverted[0] = colorVector[0] * adPrattHSItoRGB[0, 0] + colorVector[1] * adPrattHSItoRGB[0,1] + colorVector[2] * adPrattHSItoRGB[0,2];
					adConverted[1] = colorVector[0] * adPrattHSItoRGB[1,0] + colorVector[1] * adPrattHSItoRGB[1,1] + colorVector[2] * adPrattHSItoRGB[1,2];
					adConverted[2] = colorVector[0] * adPrattHSItoRGB[2,0] + colorVector[1] * adPrattHSItoRGB[2,1] + colorVector[2] * adPrattHSItoRGB[2,2];
				}
			}

			return adConverted;
		}

		public static double[] YIQConversion(double dY)
		{
			double[] adConverted = new double[3];
			adConverted[0] = Math.Max(0.0, Math.Min(1.0, dY * adYIQtoRGB[0, 0]));
			adConverted[1] = Math.Max(0.0, Math.Min(.5957, dY * adYIQtoRGB[1, 0]));
			adConverted[2] = Math.Max(0.0, Math.Min(.5226, dY * adYIQtoRGB[2, 0]));

			return adConverted;
		}

		public static double[][][] ImageToYIQ(Bitmap bmIn)
		{
			
			double[] adColor = new double[3];
			double[][][] adYIQ = new double[bmIn.Width][][];
			int k,pos;


			for (int i = 0; i < bmIn.Width; i++)
			{
				adYIQ[i] = new double[bmIn.Height][];
				for (int j = 0; j < bmIn.Height; j++)
				{
					adYIQ[i][j] = new double[3];
				}
			}

			#region Image pointer setup
			BitmapData srcBitData;
			Rectangle rectSrc = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			srcBitData = bmIn.LockBits(rectSrc, ImageLockMode.ReadOnly, bmIn.PixelFormat);
			int strideSrc = srcBitData.Stride;

			IntPtr srcPtr = srcBitData.Scan0;
			int imageBPP = Image.GetPixelFormatSize(bmIn.PixelFormat);
			int srcBytePP = imageBPP / 8;
			#endregion Image pointer setup


			unsafe
			{
				byte* sptr = (byte*)(void*)srcPtr;

				if (srcBytePP == 4)
				{
					for (int j = 0; j < bmIn.Height; j++)
					{
						k = j * strideSrc;
						for (int i = 0; i < strideSrc; i++)
						{
							pos = i % srcBytePP;
							switch (pos)
							{
								case 2:
									adColor[pos] = (double)(sptr[i + k]);
									adYIQ[i / srcBytePP][j] = YIQConversion(adColor, true);
									break;
								case 3:
									break;
								default:
									adColor[pos] = (double)(sptr[k + i]);
									break;
							}
						}
					}
				}
				else if (srcBytePP == 1)
				{
					for (int j = 0; j < bmIn.Height; j++)
					{
						k = j * strideSrc;
						for (int i = 0; i < bmIn.Width; i++)
						{
							adColor[0] = (double)(sptr[i + k]);
							adColor[1] = (double)(sptr[i + k]);
							adColor[2] = (double)(sptr[i + k]);
							adYIQ[i][j] = YIQConversion(adColor, true);
						}
					}
				}

				else
				{
					//(i,j) represents (x,y)
					for (int j = 0; j < bmIn.Height; j++)
					{
						k = j * strideSrc;
						for (int i = 0; i < strideSrc; i++)
						{

							adColor[i % 3] = (double)(sptr[k + i]); //store color in temp array
							if (i % 3 == 2) //filled up one color
							{
								adYIQ[i / srcBytePP][j] = YIQConversion(adColor, true);
							}
						}
					}
				}
			}
			bmIn.UnlockBits(srcBitData);
			return adYIQ;

		} //end ImageToYIQ


		internal static double[][] ImagetoY(Bitmap bmIn, bool p)
		{
			double[][] adY = new double[bmIn.Width][];
			int k;

			for (int i = 0; i < bmIn.Width; i++)
			{
				adY[i] = new double[bmIn.Height];
			} //end for

			#region Image pointer setup
			BitmapData srcBitData;
			Rectangle rectSrc = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			srcBitData = bmIn.LockBits(rectSrc, ImageLockMode.ReadOnly, bmIn.PixelFormat);
			int srcStride = srcBitData.Stride;

			IntPtr srcPtr = srcBitData.Scan0;
			int imageBPP = Image.GetPixelFormatSize(bmIn.PixelFormat);
			int srcBytePP = imageBPP / 8;
			#endregion Image pointer setup

			unsafe
			{
				byte* sptr = (byte*)(void*)srcPtr;

				for (int j = 0; j < bmIn.Height; j++)
				{
					k = j*srcStride;
					for (int i = 0; i < bmIn.Width; i++)
					{
						adY[i][j] = (sptr[k + i * srcBytePP] / 255.0) * adRGBtoYIQ[0, 0] + (sptr[k + i * srcBytePP + 1] / 255.0) * adRGBtoYIQ[0, 1] + (sptr[k + i * srcBytePP + 2] / 255.0) * adRGBtoYIQ[0, 2]; 
					} //end for
				} //end for
			} //end unsafe

			bmIn.UnlockBits(srcBitData);
			
			return adY;
		}//return GetYChannel
	}
}
